<?php


namespace common\service;


use common\exception\LogicException;
use support\bootstrap\Redis;
use support\ErrorCode;
use support\SingletonTrait;
use bonza\fdd\Fdd;
use common\model\ContractModel;
use think\Exception;
use \think\Response;
use \think\exception\HttpResponseException;


class FDDService
{
    /**
     * 法大大接口授权配置
     * @var Fdd
     */
    protected $fddObj;

    // 单例工具
    use SingletonTrait;

    /**
     * FDDService constructor.
     */
    public function __construct()
    {
        // 实例化法大大接口对象
        $fddConfig = C("fdd");
        $this->fddObj = new Fdd($fddConfig);
    }

    /**
     * 抛出http状态码异常
     * @param $message
     * @param int $code
     * @param int $httpCode
     */
    protected function throwHttpStatusException($message, $code = 0, $httpCode = 500)
    {
        $errorData = [
            "code" => $code,
            "msg" => $message,
        ];
        $response = new Response(json_encode($errorData), $httpCode);
        throw new HttpResponseException($response);
    }

    /**
     * 处理法大大接口异常
     * @param $fddResult
     */
    protected function handleException($fddResult)
    {
        if (!in_array((int)$fddResult['code'], [1, 2, 1000])) {
            throw_strack_exception("error: {$fddResult['code']}, {$fddResult['msg']}", ErrorCode::FDD_INTERFACE_EXCEPTION);
        }
    }

    /**
     * 上传合同模板
     * @param $param
     * @param $filePath
     * @return mixed
     */
    public function uploadContractTemplate($param, $filePath)
    {
        /**
         *
         * 只会返回成功和失败 没有其他参数  template_id 是我方约定 不可重复同一个template_id
         */
        $templateId = $param['template_id'];
        $res = $this->fddObj->uploadTemplate($templateId, $filePath, '');
        $this->handleException($res);
        return $res;
    }

    /**
     * 填充模板 生成合同
     * 生成合同 contract_id
     * @param $param
     * @return mixed
     */
    public function generateContract($param)
    {
        /**
         *
         *
         * "code": "1000",
         * "download_url": "https://testapi.fadada.com:8443/api/downLoadContract.action?app_id=403802&v=2.0&timestamp=20201023174044&contract_id=test_contract_id01&msg_digest=QTgzRUM0QjQwMUE2NjVDMUNFMkY4OTI4QkEwNzFGN0MzMzBERTI0Mg==",
         * "msg": "操作成功",
         * "result": "success",
         * "viewpdf_url": "https://testapi.fadada.com:8443/api/viewContract.action?app_id=403802&v=2.0&timestamp=20201023174044&contract_id=test_contract_id01&msg_digest=QTgzRUM0QjQwMUE2NjVDMUNFMkY4OTI4QkEwNzFGN0MzMzBERTI0Mg=="
         *
         */
        $title = $param['title'];
        $templateId = $param['template_id'];
        $contractId = $param['contract_id'];
        $paramMap = $param['parameter_map'];
        $dynamicTables = $param['dynamic_tables'] ?? [];
        $extraParam = [];

        $paramMap = json_encode($paramMap);
        $extraParam['dynamic_tables'] = json_encode($dynamicTables);
        $res = $this->fddObj->generateContract($title, $templateId, $contractId, $paramMap, $extraParam);
        $this->handleException($res);
        return $res;
    }

    /**
     * 查看模板
     * @param $param
     * @return string
     */
    public function viewTemplate($param)
    {
        $templateId = $param['template_id'];
        return $this->fddObj->viewTemplate($templateId);
    }

    /**
     * 删除模板
     * @param $param
     * @return mixed
     */
    public function deleteTemplate($param)
    {
        $templateId = $param['template_id'];
        $res = $this->fddObj->templateDelete($templateId);
        $this->handleException($res);
        return $res;
    }

    /**
     * 生成回调地址
     * @param array $redirectUrlExtraParam
     * @return string
     */
    public function buildCallBackUrl(array $redirectUrlExtraParam = [])
    {
        $notifyUrl = C("fdd.callBackUrl");
        if (!empty($redirectUrlExtraParam)) {
            if (strstr($notifyUrl, '?')) {
                $firstSep = "&";
            } else {
                $firstSep = '?';
            }
            $notifyUrl .= $firstSep;
            foreach ($redirectUrlExtraParam as $extraParamKey => $extraParamVal) {
                $notifyUrl .= $extraParamKey . "=" . $extraParamVal . "&";
            }
            $notifyUrl = rtrim($notifyUrl, '&');
        }
        return $notifyUrl;
    }

    /**
     * 自动签署合同
     * @param $param
     * @return true
     * @throws \Exception
     */
    public function extSignAuto($param)
    {
        $transactionId = $param['transaction_id'];
        $contractId = $param['contract_id'];
        $customerId = $param['customer_id'];
        $clientRole = $param['client_role'] ?? 1;
        $docTitle = $param['doc_title'];
        $positionType = $param['position_type'] ?? 0;
        $signKeyword = $param['sign_keyword'] ?? "";
        $keywordStrategy = $param['keyword_strategy'] ?? "0";
        $redirectUrlExtraParam = $param['redirect_url_extra_param'] ?? [];
        $notifyUrl = $this->buildCallBackUrl($redirectUrlExtraParam);

        $res = $this->fddObj->extSignAuto($transactionId, $contractId, $customerId, $clientRole, $docTitle, $positionType, $signKeyword, $keywordStrategy, $notifyUrl);

        if ($res['result'] != "success") {
            throw new LogicException($res['msg'] . "{$res['code']}", ErrorCode::FDD_AUTO_CONTRACT_SIGNING_FAILED);
        }


        return true;
    }

    /**
     * 合同签署回调
     * @param $param
     * @param $getParam
     * @return bool
     * @throws \Exception
     */
    public function contractExtSignAutoCallback($param, $getParam)
    {
        trace("CONTRACT SIGN CALLBACK IN", "INFO");
        trace(json_encode($param), "INFO");
        $flag = $this->fddObj->contractCallBackVerifySign($param);

        if (!$flag) {
            $this->throwHttpStatusException("msgSign error", 400, 400);
        }

        // 判断签署状态 3000 为签署通过
        if ($param['result_code'] != 3000) {
            return null;
        }

        // 重新预设xUserInfo
        if (empty($getParam['code'])) {
            $this->throwHttpStatusException("missing verify code", 400, 400);
        }
        $xuserInfoHeader = Redis::get($getParam['code']);
        if (empty($xuserInfoHeader)) {
            $this->throwHttpStatusException("invalid verify code", 400, 400);
        }

        UserService::getInstance()->parseXUserInfoAndPresetRequest($xuserInfoHeader);

        $transactionId = $param['transaction_id'];
        /** @var ContractModel $contractModel */
        $contractModel = model(ContractModel::class);
        $contract = $contractModel->where([
            ["fdd_party_a_transaction_id" => ['eq', $transactionId]],
            ["fdd_party_b_transaction_id" => ['eq', $transactionId]],
            "_logic" => "OR"
        ])->field("id,fdd_contract_id,party_a_signed_time,party_b_signed_time,status,fdd_party_a_transaction_id,fdd_party_b_transaction_id")->find();

        if (empty($contract)) {
            $this->throwHttpStatusException("contract did not exist", 400, 400);
        }

        // 判断更新甲乙方的签署时间
        if ($contract['fdd_party_a_transaction_id'] == $transactionId) {
            $contract['party_a_signed_time'] = time();
        } else {
            $contract['party_b_signed_time'] = time();
        }

        $needFilingContract = false;
        // 标记合同已经签署
        if ($contract['party_a_signed_time'] > 0 && $contract['party_b_signed_time'] > 0) {
            $contract['status'] = 'signed';
            $needFilingContract = true;
        }

        $contractModel->startTrans();
        try {
            $modifyFlag = $contractModel->save($contract);

            if (!$modifyFlag) {
                $this->throwHttpStatusException("update contract sign time failure", 400, 500);
            }
            $contractService = ContractService::getInstance();
            $contractService->handleContractSignChange($contract, $transactionId);

            // 当业务处理完成之后  进行合同归档
            if ($needFilingContract) {
                $this->contractFiling($contract['fdd_contract_id']);
            }
            $contractModel->commit();
            return true;
        } catch (HttpResponseException $e) {
            // 不重写http异常 避免异常code被替换成500
            $contractModel->rollback();
            $errorData = [
                "code" => $e->getCode(),
                "msg" => $e->getMessage(),
            ];
            APP_DEBUG && $errorData['trace'] = $e->getTrace();
            trace($e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL, 'ERR');
            throw $e;

        } catch (\Throwable $e) {
            $contractModel->rollback();
            $errorData = [
                "code" => $e->getCode(),
                "msg" => $e->getMessage(),
            ];
            APP_DEBUG && $errorData['trace'] = $e->getTrace();
            trace($e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL, 'ERR');
            throw new HttpResponseException(new Response(json_encode($errorData), 500));
        }
    }

    /**
     * 手动签署合同
     * @param $param
     * @return mixed
     */
    public function extSign($param)
    {
        $transactionId = $param['transaction_id'];
        $contractId = $param['contract_id'];
        $docTitle = $param['doc_title'];
        $customerId = $param['customer_id'];
        $returnUrl = $param['return_url'] ?? "";
        $signKeyword = $param['sign_keyword'];
        $outhCustomerId = $param['outh_customer_id'] ?? "";
        $redirectUrlExtraParam = $param['redirect_url_extra_param'] ?? [];
        $notifyUrl = $this->buildCallBackUrl($redirectUrlExtraParam);

        return $this->fddObj->extSign($transactionId, $contractId, $customerId, $docTitle, $returnUrl, $signKeyword, $notifyUrl, $outhCustomerId);
    }

    /**
     * 合同归档
     * @param $contractId
     * @return bool
     */
    private function contractFiling($contractId)
    {
        $res = $this->fddObj->contractFiling($contractId);

        if ($res['result'] != 'success') {
            $errMsg = $res['msg'] ?? "";
            $this->throwHttpStatusException($errMsg, 500, 500);
        }
        APP_DEBUG && trace("contract {$contractId} filing OK", "DEBUG");
        return true;
    }

    /**
     * 合同状态查询
     * @param $contractId
     * @return array
     */
    public function contractStatus($contractId)
    {
        $res = $this->fddObj->contractStatus($contractId);
        if ($res['result'] !== "success") {
            throw new LogicException($res['msg'], $res['code'], ErrorCode::FDD_QUERY_CONTRACT_STATUS_FAILURE);
        }
        return [
            'contract_status' => $res['contract_status'],
            'contract_status_desc' => $res['contract_status_desc'],
        ];
    }

    /**
     * 查看合同模板里可用的keys
     * @param $templateId
     * @return array
     */
    public function getContractTemplateKeys($templateId)
    {
        $res = $this->fddObj->getTemplateKeys($templateId);
        $this->handleException($res);
        return $res['data'];
    }

    /**
     * 获得授权签署地址
     * @param $param
     * @return mixed
     */
    public function beforeAuthSign($param)
    {
        $transactionId = $param['transaction_id'];
        $contractId = $param['contract_id'];
        $customerId = $param['customer_id'];
        $returnUrl = $param['return_url'] ?? "https://www.teamones.cn/";
        $redirectUrlExtraParam = $param['redirect_url_extra_param'] ?? [];
        $notifyUrl = $this->buildCallBackUrl($redirectUrlExtraParam);

        return $this->fddObj->beforeAuthSign($transactionId, $contractId, $customerId, $returnUrl, $notifyUrl);
    }

    /**
     * @param string $xUserInfoHeader
     * @throws \Exception
     */
    private function parseXUserInfoAndPresetRequest(string $xUserInfoHeader)
    {
        $request = \request();
        $loginService = LoginService::getInstance();
        $XUserInfoBase = json_decode(base64_decode($xUserInfoHeader), true);
        if (!empty($XUserInfoBase) && !empty($XUserInfoBase['id']) && !empty($XUserInfoBase['tenant_id'])) {

            $userDataIn = [
                'phone' => $XUserInfoBase['phone'],
                'email' => $XUserInfoBase['email'],
                'name' => $XUserInfoBase['name'],
                'union_id' => $XUserInfoBase['id'],
                'avatar' => !empty($XUserInfoBase['avatar']) ? $XUserInfoBase['avatar'] : "",
            ];

            $userCache = $request->getUserInfoCache($XUserInfoBase['id'], $userDataIn);

            if ($userCache !== false) {
                $userData = $userCache;
            } else {
                // 同步用户信息
                $userData = $loginService->syncUserBasicData($XUserInfoBase);
                $request->setUserInfoCache($XUserInfoBase['id'], $userData);
            }


            // 设置当前module code
            $controller = to_under_score($request->controller());


            $request->getModuleCode($controller);
            $request->getTenantId($XUserInfoBase['tenant_id']);
            $request->getUserUUID($userData['uuid']);
            $request->getUserId($userData['id']);
            $request->getUnionUserId($XUserInfoBase['id']);
            $request->getXUserInfo($xUserInfoHeader);

            // 生成当前请求批次号
            $batchNumber = C('belong_system') . '_' . string_random(8) . '-' . create_uuid();
            $request->getBatchNumber($batchNumber);
        } else {
            // 错误的header头参数
            APP_DEBUG && trace("Wrong user info param.", "DEBUG");
            throw_strack_exception("Wrong user info param.", ErrorCode::WRONG_USER_INFO_PARAM);
        }

    }
}